package org.biloxi.model.impl;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.biloxi.builders.subjects.cardsubjects.HaveCardSubject;
import org.biloxi.builders.subjects.playersubjects.WhenPlayerSubject;
import org.biloxi.commands.Command;
import org.biloxi.commands.exceptions.CommandOverriddenException;
import org.biloxi.commands.impl.CommandImpl;
import org.biloxi.game.Game;
import org.biloxi.model.card.CardInstance;
import org.biloxi.model.impl.exceptions.IllegalCommandException;
import org.biloxi.model.magicobjects.MagicObject;
import org.biloxi.model.mutable.GameStateMutable;
import org.biloxi.model.mutable.player.PlayerStateMutable;
import org.biloxi.model.mutable.player.ZoneMutable;
import org.biloxi.model.player.PlayerState;
import org.biloxi.model.player.Zone;
import org.biloxi.model.player.impl.PlayerStateImpl;
import org.biloxi.player.PlayerID;
import org.biloxi.player.actions.ActionOptionPair;

public class GameStateImpl implements GameStateMutable {

	private final List<PlayerID> _pids;
	private final Map<PlayerID,PlayerStateMutable> _playerStateMap = new HashMap<PlayerID,PlayerStateMutable>();
	private final Map<Zone,ZoneMutable> _zoneMap = new HashMap<Zone,ZoneMutable>();
	private PlayerID _turnsPlayerID = null;
	private Phase _phase;
	private final Game _game;

	/**
	 * Creates a GameState in an initial state with specified number of players.
	 * Creates and Add initial state PlayerStates.
	 * @param numPlayers
	 */
	public GameStateImpl(List<PlayerID> pids, List<List<MagicObject>> mos, Game g) {
		_game = g;
		
		_pids = pids;
		if(_pids.size() == mos.size()){
			for(int i=0; i<_pids.size();++i)
				_playerStateMap.put(_pids.get(i), new PlayerStateImpl(_pids.get(i),mos.get(i)));
		}
		
		for(PlayerStateMutable psm : _playerStateMap.values()){
			_zoneMap.put(psm.mutableGraveYard(), psm.mutableGraveYard());
			_zoneMap.put(psm.mutableHand(), psm.mutableHand());
			_zoneMap.put(psm.mutableInPlay(), psm.mutableInPlay());
			_zoneMap.put(psm.mutableLibrary(), psm.mutableLibrary());
		}
	}
	
	public ZoneMutable getMutableZone(Zone z) {
		return _zoneMap.get(z);
	}


	/**
	 * Gets the player id of whoever's turn it is.
	 * @return
	 */
	public PlayerID getTurnsPlayerID() {
		return _turnsPlayerID;
	}
	/**
	 * Sets the player id of whoever's turn it is.
	 * @return
	 */
	public void setTurnsPlayerID(PlayerID pid) {
		_turnsPlayerID = pid;
	}

	/**
	 * Gets the phase of the game.
	 * @return
	 */
	public Phase getPhase() {
		return _phase;
	}

	/**
	 * Sets the phase of the game.
	 * @return
	 */
	public void setPhase(Phase p) {
		_phase = p;
	}

	/**
	 * @return number of players
	 */
	public int numPlayers(){
		return _playerStateMap.size();
	}
	
	/**
	 * Convenience method for returning the PlayerState of the player who's turn it is.
	 * @return
	 */
	public PlayerState getTurnsPlayerState(){
		return _playerStateMap.get(getTurnsPlayerID());
	}
	
	/**
	 * Gets PlayerState of player by player id 
	 * @param id player id
	 * @return PlayerState of player by player id
	 */
	public Map<PlayerID, PlayerState> getPlayerStates(){
		Map<PlayerID, PlayerState> unmod = new HashMap<PlayerID, PlayerState>();
		for(Entry<PlayerID, PlayerStateMutable> pp : _playerStateMap.entrySet()){
			unmod.put(pp.getKey(), pp.getValue());
		}
		return Collections.unmodifiableMap(unmod);
	}
	
	/**
	 * Convenience method for retrieving PlayerState given a PlayerID.
	 * 
	 * @param pid PlayerID
	 * @return PlayerState or null if PlayerID is doesn't exist.
	 */
	public PlayerState getPlayerState(PlayerID pid) {
		return _playerStateMap.get(pid);
	}

	// TODO Unimplemented
	public HaveCardSubject getCardSubject(CardInstance c) {
		return null;
	}

	public List<PlayerID> getPlayerIDs() {
		return Collections.unmodifiableList(_pids);
	}

	// TODO Unimplemented
	public WhenPlayerSubject getPlayerSubject() {
		return null;
	}

	public Set<PlayerID> getOppenents(PlayerID pid) {
		Set<PlayerID> ops = new HashSet<PlayerID>();
		for(PlayerID p : _playerStateMap.keySet()){
			if( p != pid )
				ops.add(p);
		}
		return Collections.unmodifiableSet(ops); //
	}

	/**
	 * Gets Opponent PlayerStates of a player  
	 * @param id player id
	 * @return PlayerState of player by player id
	 */
	public Map<PlayerID, PlayerState> getOpponentStates(String pid){
		if(_playerStateMap.containsKey(pid)){
			Map<PlayerID,PlayerState> opmap = new HashMap<PlayerID,PlayerState>(_playerStateMap);
			opmap.remove(pid);
			return opmap;
		}else{
			return null;//TODO create custom exception
		}
	}

	public PlayerStateMutable mutablePlayerState(PlayerID pid) {
		return _playerStateMap.get(getTurnsPlayerID());
	}

	public Map<PlayerID, PlayerStateMutable> mutablePlayerStates() {
		return _playerStateMap;
	}

	public <O, R> R do_(Command<O, R> cmd, O option) throws CommandOverriddenException {
		// This Class cast ensures that ONLY legal Commands are run.
		// It does this since all Commands we implement extend CommandImpl.
		// This prevents some goober from implementing their own Command to
		// change the model.
		try{
			return ((CommandImpl<O, R>)cmd).execute(option, this);
		}catch(ClassCastException e){
			throw new IllegalCommandException("A Non-Biloxi Command attempted execute.");
		}
	}

	public List<MagicObject> getType(String type, List<MagicObject> mos) {
		List<MagicObject> tmpMos = new LinkedList<MagicObject>();
		for(MagicObject o : mos){
			if(o.getType().equals(type)){
				tmpMos.add(o);
			}
		}
		return tmpMos;
	}
		
}
